﻿#if false
// ProductionAI.cs
//
// Copyright (c) 2008 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

using Supremacy.Annotations;
using Supremacy.Buildings;
using Supremacy.Collections;
using Supremacy.Economy;
using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.Pathfinding;
using Supremacy.Tech;
using Supremacy.Types;
using Supremacy.Universe;
using Supremacy.Utility;

using System.Linq.Expressions;

namespace Supremacy.AI
{
    public static class ProductionActionExtensions
    {
        #region Methods
        public static IProductionAction ActivateBuilding(
            [NotNull] this IProductionAction action,
            [NotNull] BuildingDesign buildingDesign)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (buildingDesign == null)
                throw new ArgumentNullException("buildingDesign");

            return new RecalculateResourceProduction(
                new ModifyStructuresAction(
                    action,
                    action.Structures.ActivateBuilding(buildingDesign)));
        }

        public static IProductionAction ActivateBuilding(
            [NotNull] this IProductionAction action,
            [NotNull] BuildingSnapshot building)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (building == null)
                throw new ArgumentNullException("building");

            return new RecalculateResourceProduction(
                new ModifyStructuresAction(
                    action,
                    action.Structures.ActivateBuilding(building)));
        }

        public static IProductionAction AddBuilding(
            [NotNull] this IProductionAction action,
            [NotNull] BuildingDesign buildingDesign)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (buildingDesign == null)
                throw new ArgumentNullException("buildingDesign");

            return new ModifyStructuresAction(
                action,
                action.Structures.AddBuilding(buildingDesign));
        }

        public static IProductionAction WaitForTurn(
            [NotNull] this IProductionAction action)
        {
            return action.WaitForTurns(1);
        }

        public static IProductionAction WaitForTurns(
            [NotNull] this IProductionAction action,
            int numberOfTurns)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (numberOfTurns < 0)
                throw new ArgumentOutOfRangeException("numberOfTurns", "value must be >= 0");

            return new WaitForTurnsAction(action, numberOfTurns);
        }

        public static IProductionAction EnqueueBuildProject(
            [NotNull] this IProductionAction action,
            [NotNull] TechObjectDesign design)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (design == null)
                throw new ArgumentNullException("design");

            return new EnqueueBuildProjectAction(action, design);
        }

        public static IProductionAction AllocateLabor(
            [NotNull] this IProductionAction action,
            ProductionCategory category,
            int count)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            var maxAllocatable = action.Structures.GetTotalFacilities(category)
                                 - action.LaborAllocation.AllocatedUnits[category];

            if (maxAllocatable <= 0)
            {
                throw new InvalidOperationException(
                    String.Format(
                        "There are not enough {0} facilities to allocate {1} labor.",
                        category,
                        count));
            }

            if (count > action.LaborAllocation.UnallocatedUnits)
            {
                throw new InvalidOperationException(
                    String.Format(
                        "There is not enough available labor to activate {0} {1} facilities.",
                        count,
                        category));
            }

            return new ReallocateLaborAction(action, action.LaborAllocation.Allocate(category, count));
        }

        public static IProductionAction AllocateLabor(
            [NotNull] this IProductionAction action,
            ProductionCategory category)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            return action.AllocateLabor(category, 1);
        }

        public static IProductionAction DeactivateBuilding(
            [NotNull] this IProductionAction action,
            [NotNull] BuildingDesign buildingDesign)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (buildingDesign == null)
                throw new ArgumentNullException("buildingDesign");

            return new ModifyStructuresAction(
                action,
                action.Structures.DeactivateBuilding(buildingDesign));
        }

        public static IProductionAction DeactivateBuilding(
            [NotNull] this IProductionAction action,
            [NotNull] BuildingSnapshot building)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (building == null)
                throw new ArgumentNullException("building");

            return new ModifyStructuresAction(
                action,
                action.Structures.DeactivateBuilding(building));
        }

        public static IProductionAction DeallocateLabor(
            [NotNull] this IProductionAction action,
            ProductionCategory category,
            int count)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            
            var maxDeallocatable = action.LaborAllocation.AllocatedUnits[category];
            if (count > maxDeallocatable)
            {
                throw new InvalidOperationException(
                    String.Format(
                        "You cannot deactivate {0} {1} facilities because that many are not active.",
                        count,
                        category));
            }

            return new ReallocateLaborAction(
                action,
                action.LaborAllocation.Deallocate(category, count));
        }

        public static IProductionAction DeallocateLabor(
            [NotNull] this IProductionAction action,
            ProductionCategory category)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            return action.DeallocateLabor(category, 1);
        }

        public static IProductionAction GetInitialAction(
            [NotNull] this IProductionAction action)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            return action.BaseAction ?? action;
        }

        public static IProductionAction RemoveBuilding(
            [NotNull] this IProductionAction action,
            [NotNull] BuildingDesign buildingDesign)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (buildingDesign == null)
                throw new ArgumentNullException("buildingDesign");

            var building = action.Structures.Buildings.FirstOrDefault(o => o.Design == buildingDesign);
            if ((building != null) && building.IsActive)
            {
                return new ModifyStructuresAction(
                    action.DeactivateBuilding(building),
                    action.Structures.RemoveBuilding(buildingDesign));
            }

            return new ModifyStructuresAction(
                action,
                action.Structures.RemoveBuilding(buildingDesign));
        }

        public static IProductionAction SetFacilityDesign(
            [NotNull] this IProductionAction action,
            [NotNull] ProductionFacilityDesign facilityDesign)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            if (facilityDesign == null)
                throw new ArgumentNullException("facilityDesign");

            return new ModifyStructuresAction(
                action,
                action.Structures.SetFacilityDesign(facilityDesign));
        }

        public static int GetNetProduction(
            [NotNull] this IProductionAction action,
            ProductionCategory category)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            var totalOutput = action.GetProductionOutput(category);
            switch (category)
            {
                case ProductionCategory.Energy:
                    return (totalOutput -
                            action.Structures.Buildings
                                .Where(o => o.IsActive)
                                .Sum(o => o.Design.EnergyCost));
                case ProductionCategory.Food:
                    return (totalOutput - action.CurrentPopulation);
                default:
                    return totalOutput;
            }
        }

        public static int GetFutureNetProduction(
            [NotNull] this IProductionAction action,
            ProductionCategory category)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            var totalOutput = action.GetFutureProductionOutput(category);
            switch (category)
            {
                case ProductionCategory.Energy:
                    return (totalOutput -
                            action.Structures.Buildings
                                .Where(o => o.IsActive)
                                .Sum(o => o.Design.EnergyCost));
                case ProductionCategory.Food:
                    return (totalOutput - action.CurrentPopulation);
                default:
                    return totalOutput;
            }
        }

        public static int GetBaseResourceOutput(
            [NotNull] this IProductionAction action,
            ResourceType resourceType)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            return action.Colony.GetBaseResourceProduction(
                resourceType,
                action.CurrentPopulation);
        }

        public static int GetResourceOutput(
            [NotNull] this IProductionAction action,
            ResourceType resourceType)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            var baseOutput = action.Colony.GetBaseResourceProduction(
                resourceType,
                action.CurrentPopulation);

            var modifier = new ValueModifier();

            switch (resourceType)
            {
                case ResourceType.Deuterium:
                    modifier.Offset = action.Structures.Buildings
                        .Where(b => b.IsActive)
                        .SelectMany(b => b.Design.Bonuses)
                        .Where(b => b.BonusType == BonusType.Deuterium)
                        .Sum(b => b.Amount);
                    modifier.Multiplier += action.Structures.Buildings
                        .Where(b => b.IsActive)
                        .SelectMany(b => b.Design.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentDeuterium)
                        .Sum(b => 0.01f * b.Amount);
                    break;

                case ResourceType.Dilithium:
                    modifier.Offset = action.Structures.Buildings
                        .Where(b => b.IsActive)
                        .SelectMany(b => b.Design.Bonuses)
                        .Where(b => b.BonusType == BonusType.Dilithium)
                        .Sum(b => b.Amount);
                    break;

                case ResourceType.RawMaterials:
                    modifier.Offset = action.Structures.Buildings
                        .Where(b => b.IsActive)
                        .SelectMany(b => b.Design.Bonuses)
                        .Where(b => b.BonusType == BonusType.RawMaterials)
                        .Sum(b => b.Amount);
                    modifier.Multiplier += action.Structures.Buildings
                        .Where(b => b.IsActive)
                        .SelectMany(b => b.Design.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentRawMaterials)
                        .Sum(b => 0.01f * b.Amount);
                    break;

/*
                case ResourceType.Personnel:
                    modifier.Offset = action.Structures.Buildings
                        .Where(b => b.IsActive)
                        .SelectMany(b => b.Design.Bonuses)
                        .Where(b => b.BonusType == BonusType.Personnel)
                        .Sum(b => b.Amount);
                    modifier.Multiplier += action.Structures.Buildings
                        .Where(b => b.IsActive)
                        .SelectMany(b => b.Design.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentPersonnelTraining)
                        .Sum(b => 0.01f * b.Amount);
                    break;
*/
            }
            return modifier.Apply(baseOutput);
        }

        public static IProductionAction Nop([NotNull] this IProductionAction action)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            return new NopAction(action);
        }
        #endregion
    }

    [ImmutableObject(true)]
    public sealed class BuildProjectSnapshot
    {
        #region Fields
        private int _turnsElapsed;
        private readonly int _designId;
        private readonly int _industryInvested;
        private readonly bool _isUpgrade;
        private readonly ResourceValueCollection _resourcesInvested;
        #endregion

        #region Constructors
        private BuildProjectSnapshot(
            int designId,
            bool isUpgrade,
            int industryInvested,
            [NotNull] ResourceValueCollection resourcesInvested)
        {
            if (resourcesInvested == null)
                throw new ArgumentNullException("resourcesInvested");

            _designId = designId;
            _isUpgrade = isUpgrade;
            _industryInvested = industryInvested;
            _resourcesInvested = resourcesInvested.Clone();
        }
        #endregion

        #region Properties and Indexers
        public int IndustryInvested
        {
            get { return _industryInvested; }
        }

        public ResourceValueCollection ResourcesInvested
        {
            get { return _resourcesInvested; }
        }

        public TechObjectDesign Design
        {
            get { return GameContext.Current.TechDatabase[_designId]; }
        }

        public int EstimatedTurnsRemaining
        {
            get
            {
                if (IsComplete)
                    return 0;
                if ((_industryInvested == 0) || (_turnsElapsed == 0))
                    return 1;
                return Math.Max(
                    ((this.Design.BuildCost - _industryInvested) /
                     (_industryInvested / _turnsElapsed)) + 1,
                    1);
            }
        }

        public bool IsComplete
        {
            get
            {
                var industryRequired = _isUpgrade ? (Design.BuildCost / 2) : Design.BuildCost;
                if (_industryInvested < industryRequired)
                    return false;

                return !EnumHelper.GetValues<ResourceType>().Any(
                    r => _resourcesInvested[r] < Design.BuildResourceCosts[r]);
            }
        }
        #endregion

        #region Methods
        public static BuildProjectSnapshot CreateBuildProject([NotNull] TechObjectDesign design)
        {
            if (design == null)
                throw new ArgumentNullException("design");

            return new BuildProjectSnapshot(
                design.DesignID,
                false,
                0,
                new ResourceValueCollection());
        }

        public static BuildProjectSnapshot CreateFacilityUpgradeProject(
            [NotNull] ProductionFacilityDesign upgradedDesign,
            int count)
        {
            if (upgradedDesign == null)
                throw new ArgumentNullException("upgradedDesign");

            return new BuildProjectSnapshot(
                upgradedDesign.DesignID,
                true,
                count * (upgradedDesign.BuildCost / 2),
                new ResourceValueCollection());
        }

        public BuildProjectSnapshot InvestIndustry(int amount)
        {
            return new BuildProjectSnapshot(
                _designId,
                _isUpgrade,
                _industryInvested + amount,
                _resourcesInvested.Clone()) { _turnsElapsed = this._turnsElapsed + 1 };
        }

        public BuildProjectSnapshot InvestResource(ResourceType resource, int amount)
        {
            var resources = _resourcesInvested.Clone();

            resources[resource] += amount;

            return new BuildProjectSnapshot(
                _designId,
                _isUpgrade,
                _industryInvested + amount,
                resources);
        }
        #endregion
    }

    #region BuildingSnapshot Nested Type
    [ImmutableObject(true)]
    public sealed class BuildingSnapshot : IEquatable<BuildingSnapshot>
    {
        #region Fields
        private readonly BuildingDesign _buildingDesign;
        private readonly bool _isActive;
        #endregion

        #region Constructors
        // ReSharper disable SuggestBaseTypeForParameter
        public BuildingSnapshot([NotNull] BuildingDesign design, bool isActive)
        {
            if (design == null)
                throw new ArgumentNullException("design");

            _buildingDesign = design;
            _isActive = isActive || design.AlwaysOnline;
        }
        // ReSharper restore SuggestBaseTypeForParameter

        public BuildingSnapshot([NotNull] Building building)
        {
            if (building == null)
                throw new ArgumentNullException("building");

            _buildingDesign = building.BuildingDesign;
            _isActive = building.IsActive || building.BuildingDesign.AlwaysOnline;
        }
        #endregion

        #region Properties and Indexers
        public BuildingDesign Design
        {
            get { return _buildingDesign; }
        }

        public bool IsActive
        {
            get { return _isActive; }
        }
        #endregion

        #region Methods
        public override bool Equals(object obj)
        {
            if (!(obj is BuildingSnapshot))
                return false;
            return Equals((BuildingSnapshot)obj);
        }

        public override int GetHashCode()
        {
            return _buildingDesign.DesignID;
        }
        #endregion

        #region IEquatable<BuildingSnapshot> Members
        public bool Equals(BuildingSnapshot other)
        {
            return (other._buildingDesign == _buildingDesign);
        }
        #endregion
    }
    #endregion

    #region ColonyStructuresSnapshot Nested Type
    [ImmutableObject(true)]
    public sealed class ColonyStructuresSnapshot
    {
        #region Fields
        private readonly ColonyStructuresSnapshot _baseSnapshot;
        private readonly BuildingSnapshot[] _buildings;
        private readonly int[] _facilityDesignIds;
        private readonly byte[] _totalFacilities;
        #endregion

        #region Constructors
        public ColonyStructuresSnapshot([NotNull] Colony colony)
        {
            if (colony == null)
                throw new ArgumentNullException("colony");

            var shipyard = colony.Shipyard;
            if (shipyard != null)
                _buildings = colony.Buildings.Select(o => new BuildingSnapshot(o)).Concat(new[] { new BuildingSnapshot(shipyard) }).ToArray();
            else
                _buildings = colony.Buildings.Select(o => new BuildingSnapshot(o)).ToArray();

            _baseSnapshot = null;
            _facilityDesignIds = new int[EnumHelper.GetValues<ProductionCategory>().Length];
            _totalFacilities = new byte[_facilityDesignIds.Length];

            foreach (var category in EnumHelper.GetValues<ProductionCategory>())
            {
                _facilityDesignIds[(int)category] = colony.GetFacilityType(category).DesignID;
                _totalFacilities[(int)category] = (byte)colony.GetTotalFacilities(category);
            }
        }

        private ColonyStructuresSnapshot(
            [NotNull] ColonyStructuresSnapshot baseSnapshot,
            [CanBeNull] IEnumerable<BuildingSnapshot> buildings)
        {
            if (baseSnapshot == null)
                throw new ArgumentNullException("baseSnapshot");

            _baseSnapshot = baseSnapshot;
            _buildings = (buildings ?? Enumerable.Empty<BuildingSnapshot>()).ToArray();
            _facilityDesignIds = baseSnapshot._facilityDesignIds;
            _totalFacilities = baseSnapshot._totalFacilities;
        }
        #endregion

        #region Properties and Indexers
        public ColonyStructuresSnapshot BaseSnapshot
        {
            get { return _baseSnapshot; }
        }

        public IEnumerable<BuildingSnapshot> Buildings
        {
            get { return _buildings; }
        }
        #endregion

        #region Methods
        public ColonyStructuresSnapshot ActivateBuilding([NotNull] BuildingDesign buildingDesign)
        {
            var buildings = this._buildings.ToList();

            var index = buildings.FindIndex(o => o.Design == buildingDesign);
            if (index >= 0)
                buildings[index] = new BuildingSnapshot(buildingDesign, true);

            return new ColonyStructuresSnapshot(this, buildings);
        }

        public ColonyStructuresSnapshot ActivateBuilding([NotNull] BuildingSnapshot building)
        {
            if (building == null)
                throw new ArgumentNullException("building");

            return ActivateBuilding(building.Design);
        }

        public ColonyStructuresSnapshot AddBuilding([NotNull] BuildingDesign buildingDesign)
        {
            return new ColonyStructuresSnapshot(
                this,
                this._buildings.Concat(
                    Enumerable.Empty<BuildingSnapshot>().DefaultIfEmpty(
                        new BuildingSnapshot(
                            buildingDesign,
                            buildingDesign.AlwaysOnline))));
        }

        public ColonyStructuresSnapshot DeactivateBuilding([NotNull] BuildingDesign buildingDesign)
        {
            var buildings = this._buildings.ToList();
            
            var index = buildings.FindIndex(o => o.Design == buildingDesign);
            if (index >= 0)
                buildings[index] = new BuildingSnapshot(buildingDesign, false);

            return new ColonyStructuresSnapshot(this, buildings);
        }

        public ColonyStructuresSnapshot DeactivateBuilding([NotNull] BuildingSnapshot building)
        {
            if (building == null)
                throw new ArgumentNullException("building");

            return DeactivateBuilding(building.Design);
        }

        public ProductionFacilityDesign GetFacilityDesign(ProductionCategory category)
        {
            return GameContext.Current.TechDatabase.ProductionFacilityDesigns[_facilityDesignIds[(int)category]];
        }

        public int GetTotalFacilities(ProductionCategory category)
        {
            return _totalFacilities[(int)category];
        }

        public ColonyStructuresSnapshot RemoveBuilding([NotNull] BuildingDesign buildingDesign)
        {
            return new ColonyStructuresSnapshot(
                this,
                _buildings.Where(o => o.Design != buildingDesign).ToArray());
        }

        public ColonyStructuresSnapshot SetFacilityDesign(
            [NotNull] ProductionFacilityDesign design)
        {
            if (design == null)
                throw new ArgumentNullException("design");

            var newSnapshot = new ColonyStructuresSnapshot(
                this,
                Enumerable.Empty<BuildingSnapshot>());

            newSnapshot._facilityDesignIds[(int)design.Category] = design.DesignID;

            return newSnapshot;
        }
        #endregion
    }
    #endregion

    #region INetProductionChange Nested Type
    public interface INetProductionChange
    {
        #region Properties and Indexers
        int this[ProductionCategory category] { get; }
        #endregion
    }
    #endregion

    #region InitialProductionAction Nested Type
    [DebuggerDisplay("{GetDebugText()}")]
    public sealed class InitialProductionAction : ProductionActionBase
    {
        #region Constructors
        public InitialProductionAction(Colony colony)
        {
            this.StartTurn = GameContext.Current.TurnNumber;
            this.Structures = new ColonyStructuresSnapshot(colony);
            this.CurrentGrowthRate = colony.GrowthRate;
            this.CurrentPopulation = colony.Population.CurrentValue;
            this.ResourceLevels = colony.GetEstimatedResourcesProduction();
            this.LaborAllocation = new LaborAllocation(colony);
            this.NetProductionChange = new NetProductionChangeInternal(this);
            this.Owner = colony.Owner;
            this.Colony = colony;
        }
        #endregion

        protected override string GetDebugText()
        {
            return string.Format(
                "{{InitialProductionAction Colony={0}}}",
                this.Colony.Name);
        }
    }
    #endregion

    #region IProductionAction Nested Type
    public interface IProductionAction
    {
        #region Properties and Indexers
        Civilization Owner { get; }
        Colony Colony { get; }
        IDeque<BuildProjectSnapshot> BuildProjects { get; }
        TurnNumber StartTurn { get; }
        TurnNumber CurrentTurn { get; }
        int ActionDuration { get; }
        int TotalDuration { get; }
        ColonyStructuresSnapshot Structures { get; }
        ResourceValueCollection ResourceLevels { get; }
        LaborAllocation LaborAllocation { get; }
        INetProductionChange NetProductionChange { get; }
        IProductionAction BaseAction { get; }
        Percentage CurrentGrowthRate { get; }
        int CurrentPopulation { get; }
        #endregion

        #region Methods
        int GetProductionOutput(ProductionCategory category);
        int GetFutureProductionOutput(ProductionCategory category);
        #endregion
    }
    #endregion

    #region LaborAllocation Nested Type
    [ImmutableObject(true)]
    public sealed class LaborAllocation
    {
        #region Fields
        private readonly ILaborAllocationAccessor _accessor;
        private readonly byte[] _allocatedUnits;
        private readonly byte _unallocatedUnits;
        #endregion

        #region Constructors
        public LaborAllocation()
        {
            _accessor = new LaborAllocationAccessor(this);
            _allocatedUnits = new byte[EnumHelper.GetValues<ProductionCategory>().Length];
        }

        public LaborAllocation([NotNull] Colony colony)
            : this()
        {
            if (colony == null)
                throw new ArgumentNullException("colony");

            EnumHelper.GetValues<ProductionCategory>()
                .ForEach(c => _allocatedUnits[(int)c] = (byte)colony.GetActiveFacilities(c));

            _unallocatedUnits = (byte)colony.GetAvailableLabor();
        }

        private LaborAllocation(byte unallocatedUnits, byte[] allocatedUnits)
            : this()
        {
            if (allocatedUnits == null)
                throw new ArgumentNullException("allocatedUnits");

            _unallocatedUnits = unallocatedUnits;

            Buffer.BlockCopy(
                allocatedUnits,
                0,
                _allocatedUnits,
                0,
                allocatedUnits.Length);
        }
        #endregion

        #region Properties and Indexers
        public ILaborAllocationAccessor AllocatedUnits
        {
            get { return _accessor; }
        }

        public int UnallocatedUnits
        {
            get { return _unallocatedUnits; }
        }

        public int TotalUnits
        {
            get
            {
                return _unallocatedUnits +
                       EnumHelper.GetValues<ProductionCategory>().Sum(o => _accessor[o]);
            }
        }
        #endregion

        #region Methods
        public LaborAllocation Allocate(ProductionCategory category)
        {
            return Allocate(category, 1);
        }

        public LaborAllocation Allocate(ProductionCategory category, int count)
        {
            var unallocatedUnits = _unallocatedUnits;
            var allocatedUnits = new byte[_allocatedUnits.Length];

            Buffer.BlockCopy(_allocatedUnits, 0, allocatedUnits, 0,
                _allocatedUnits.Length);

            var unitsAllocated = (byte)Math.Min(Math.Max(0, Math.Min(count, count)), byte.MaxValue);

            unallocatedUnits -= unitsAllocated;
            allocatedUnits[(int)category] += unitsAllocated;

            return new LaborAllocation(unallocatedUnits, allocatedUnits);
        }

        public LaborAllocation Deallocate(ProductionCategory category)
        {
            return Deallocate(category, 1);
        }

        public LaborAllocation Deallocate(ProductionCategory category, int count)
        {
            var unallocatedUnits = _unallocatedUnits;
            var allocatedUnits = new byte[_allocatedUnits.Length];

            Buffer.BlockCopy(_allocatedUnits, 0, allocatedUnits, 0, _allocatedUnits.Length);

            var unitsDeallocated = (byte)Math.Min(Math.Max(0, allocatedUnits[(int)category] - count), byte.MaxValue);

            unallocatedUnits += unitsDeallocated;
            allocatedUnits[(int)category] -= unitsDeallocated;

            return new LaborAllocation(unallocatedUnits, allocatedUnits);
        }

        public LaborAllocation AddUnallocatedUnits(int count)
        {
            var allocatedUnits = new byte[_allocatedUnits.Length];

            Buffer.BlockCopy(_allocatedUnits, 0, allocatedUnits, 0, _allocatedUnits.Length);

            return new LaborAllocation((byte)(_unallocatedUnits + count), allocatedUnits);
        }
        #endregion

        #region ILaborAllocationAccessor Nested Type
        public interface ILaborAllocationAccessor
        {
            #region Properties and Indexers
            int this[ProductionCategory category] { get; }
            #endregion
        }
        #endregion

        #region LaborAllocationAccessor Nested Type
        private class LaborAllocationAccessor : ILaborAllocationAccessor
        {
            #region Fields
            private readonly LaborAllocation _allocation;
            #endregion

            #region Constructors
            public LaborAllocationAccessor([NotNull] LaborAllocation allocation)
            {
                if (allocation == null)
                    throw new ArgumentNullException("allocation");

                _allocation = allocation;
            }
            #endregion

            #region ILaborAllocationAccessor Members
            public int this[ProductionCategory category]
            {
                get { return _allocation._allocatedUnits[(int)category]; }
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region ProductionActionBase Nested Type
    [DebuggerDisplay("{{GetDebugText()}}")]
    public abstract class ProductionActionBase : IProductionAction
    {
        #region Fields
        private int _colonyId;
        private int _ownerId;
        private readonly int _baseTotalDuration;
        private IDeque<BuildProjectSnapshot> _buildProjects;
        private LaborAllocation _laborAllocation;
        private INetProductionChange _netProductionChange;
        private ResourceValueCollection _resourceLevels;
        private ColonyStructuresSnapshot _structures;
        #endregion

        #region Constructors
        protected ProductionActionBase() : this(null) {}

        protected ProductionActionBase(IProductionAction baseAction)
        {
            StartTurn = TurnNumber.Undefined;

            BaseAction = baseAction;
            LaborAllocation = null;
            NetProductionChange = new NetProductionChangeInternal(this);
            ResourceLevels = null;
            Structures = null;

            if (baseAction == null)
                return;

            CurrentGrowthRate = baseAction.CurrentGrowthRate;
            CurrentPopulation = baseAction.CurrentPopulation;

            _structures = baseAction.Structures;
            _buildProjects = baseAction.BuildProjects;
            _resourceLevels = baseAction.ResourceLevels.Clone();
            _laborAllocation = baseAction.LaborAllocation;
            _ownerId = baseAction.Owner.CivID;
            _colonyId = baseAction.Colony.ObjectID;
            _baseTotalDuration = baseAction.TotalDuration;
        }
        #endregion

        #region IProductionAction Members
        public Civilization Owner
        {
            get { return GameContext.Current.Civilizations[_ownerId]; }
            protected set { _ownerId = (value != null) ? value.CivID : Civilization.InvalidID; }
        }

        public Colony Colony
        {
            get { return GameContext.Current.Universe.Objects[_colonyId] as Colony; }
            protected set { _colonyId = (value != null) ? value.ObjectID : GameObjectID.InvalidID; }
        }

        public IDeque<BuildProjectSnapshot> BuildProjects
        {
            get
            {
                return _buildProjects ??
                       ((BaseAction != null) ? BaseAction.BuildProjects : ImmutableDeque<BuildProjectSnapshot>.Empty);
            }
            protected set { _buildProjects = value; }
        }

        public TurnNumber StartTurn { get; protected set; }

        public TurnNumber CurrentTurn
        {
            get { return (this.StartTurn + this.TotalDuration); }
        }

        public virtual int ActionDuration
        {
            get { return 0; }
        }

        public int TotalDuration
        {
            get { return (_baseTotalDuration + this.ActionDuration); }
        }

        public ColonyStructuresSnapshot Structures
        {
            get
            {
                return _structures ??
                       ((BaseAction != null) ? BaseAction.Structures : null);
            }
            protected set { _structures = value; }
        }

        public ResourceValueCollection ResourceLevels
        {
            get
            {
                return _resourceLevels ??
                       ((BaseAction != null) ? BaseAction.ResourceLevels : null);
            }
            protected set { _resourceLevels = value; }
        }

        public LaborAllocation LaborAllocation
        {
            get
            {
                return _laborAllocation ??
                       ((BaseAction != null) ? BaseAction.LaborAllocation : null);
            }
            protected set { _laborAllocation = value; }
        }

        public INetProductionChange NetProductionChange
        {
            get
            {
                return _netProductionChange ??
                       ((BaseAction != null) ? BaseAction.NetProductionChange : null);
            }
            protected set { _netProductionChange = value; }
        }

        public IProductionAction BaseAction { get; protected set; }

        public Percentage CurrentGrowthRate { get; protected set; }

        public int CurrentPopulation { get; protected set; }
        #endregion

        #region Methods
        protected virtual string GetDebugText()
        {
            return this.GetType().Name;
        }

        public int GetProductionOutput(ProductionCategory category)
        {
            return Math.Max(0, GetProductionOutputOverride(category));
        }

        public int GetFutureProductionOutput(ProductionCategory category)
        {
            var baseOutput = GetProductionOutputOverride(category);
            var modifier = new ValueModifier();
            switch (category)
            {
                case ProductionCategory.Food:
                    modifier.Offset += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Food)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentFood)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Industry:
                    modifier.Offset += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Industry)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentIndustry)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Energy:
                    modifier.Offset += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Energy)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentEnergy)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Intelligence:
                    modifier.Offset += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Intelligence)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentIntelligence)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Research:
                    modifier.Offset += this.BuildProjects
                        .Select(o => o.Design)
                        .OfType<BuildingDesign>()
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Research)
                        .Sum(o => o.Amount);
                    break;
            }
            return modifier.Apply(baseOutput);
        }

        protected virtual int GetProductionOutputOverride(ProductionCategory category)
        {
            var baseOutput = this.LaborAllocation.AllocatedUnits[category] *
                             this.Structures.GetFacilityDesign(category).UnitOutput;

            var modifier = new ValueModifier();

            switch (category)
            {
                case ProductionCategory.Food:
                    modifier.Offset += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Food)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentFood)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Industry:
                    modifier.Offset += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Industry)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentIndustry)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Energy:
                    modifier.Offset += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Energy)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentEnergy)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Intelligence:
                    modifier.Offset += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Intelligence)
                        .Sum(o => o.Amount);
                    modifier.Multiplier += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.PercentIntelligence)
                        .Sum(o => 0.01f * o.Amount);
                    break;

                case ProductionCategory.Research:
                    modifier.Offset += this.Structures.Buildings
                        .Where(o => o.IsActive)
                        .Select(o => o.Design)
                        .SelectMany(d => d.Bonuses)
                        .Where(b => b.BonusType == BonusType.Research)
                        .Sum(o => o.Amount);
                    break;
            }
            return modifier.Apply(baseOutput);
        }

        public override string ToString()
        {
            return GetType().Name;
        }
        #endregion

        #region NetProductionChangeInternal Nested Type
        [ImmutableObject(true)]
        protected sealed class NetProductionChangeInternal : INetProductionChange
        {
            #region Fields
            private readonly IProductionAction _action;
            #endregion

            #region Constructors
            public NetProductionChangeInternal([NotNull] IProductionAction action)
            {
                if (action == null)
                    throw new ArgumentNullException("action");
                _action = action;
            }
            #endregion

            #region INetProductionChange Members
            public int this[ProductionCategory category]
            {
                get
                {
                    if (_action.BaseAction == null)
                        return 0;
                    return (_action.GetProductionOutput(category) -
                            _action.BaseAction.GetProductionOutput(category));
                }
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region ReallocateLaborAction Nested Type
    public sealed class ReallocateLaborAction : ProductionActionBase
    {
        #region Constructors
        public ReallocateLaborAction(IProductionAction baseAction, [NotNull] LaborAllocation laborAllocation)
            : base(baseAction)
        {
            if (laborAllocation == null)
                throw new ArgumentNullException("laborAllocation");

            LaborAllocation = laborAllocation;
        }
        #endregion
    }
    #endregion

    [DebuggerDisplay("{GetDebugText()}")]
    public sealed class EnqueueBuildProjectAction : ProductionActionBase
    {
        private readonly BuildProjectSnapshot _newBuildProject;

        public EnqueueBuildProjectAction([NotNull] IProductionAction baseAction, [NotNull] TechObjectDesign design)
            : base(baseAction)
        {
            if (design == null)
                throw new ArgumentNullException("design");

            _newBuildProject = BuildProjectSnapshot.CreateBuildProject(design);
            this.BuildProjects = baseAction.BuildProjects.EnqueueRight(_newBuildProject);
        }

        protected override string GetDebugText()
        {
            return string.Format(
                "{{EnqueueBuildProjectAction Design={0}}}",
                _newBuildProject.Design.Key);
        }
    }

    public sealed class ModifyStructuresAction : ProductionActionBase
    {
        #region Constructors
        public ModifyStructuresAction(IProductionAction baseAction, [NotNull] ColonyStructuresSnapshot structures)
            : base(baseAction)
        {
            if (structures == null)
                throw new ArgumentNullException("structures");

            this.Structures = structures;
        }
        #endregion
    }

    public sealed class RecalculateResourceProduction : ProductionActionBase
    {
        #region Constructors
        public RecalculateResourceProduction(IProductionAction baseAction)
            : base(baseAction)
        {
            // TODO: Fix this formula to include natural base values.
            var resourceLevels = new ResourceValueCollection();
            var buildings = Structures.Buildings;
            var getModifier =
                (Func<ResourceType, ValueModifier>)
                delegate(ResourceType resourceType)
                {
                    var modifier = new ValueModifier
                                   {
                                       HasCompoundMultiplier = false,
                                       IsOffsetAppliedFirst = true
                                   };
                    switch (resourceType)
                    {
                        case ResourceType.Deuterium:
                            modifier.Offset += buildings
                                .Where(o => o.IsActive)
                                .Select(
                                o => o.Design.Bonuses
                                         .Where(b => b.BonusType == BonusType.Deuterium)
                                         .Select(b => b.Amount)
                                         .Sum())
                                .Sum();
                            modifier.Multiplier += 0.01f
                                                   * buildings
                                                         .Where(o => o.IsActive)
                                                         .Select(
                                                         o => o.Design.Bonuses
                                                                  .Where(b => b.BonusType == BonusType.PercentDeuterium)
                                                                  .Select(b => b.Amount)
                                                                  .Sum())
                                                         .Sum();
                            break;
                        case ResourceType.Dilithium:
                            modifier.Offset += buildings
                                .Where(o => o.IsActive)
                                .Select(
                                o => o.Design.Bonuses
                                         .Where(b => b.BonusType == BonusType.Dilithium)
                                         .Select(b => b.Amount)
                                         .Sum())
                                .Sum();
                            break;
/*
                        case ResourceType.Personnel:
                            modifier.Offset += buildings
                                .Where(o => o.IsActive)
                                .Select(
                                o => o.Design.Bonuses
                                         .Where(b => b.BonusType == BonusType.Personnel)
                                         .Select(b => b.Amount)
                                         .Sum())
                                .Sum();
                            modifier.Multiplier += 0.01f
                                                   * buildings
                                                         .Where(o => o.IsActive)
                                                         .Select(
                                                         o => o.Design.Bonuses
                                                                  .Where(
                                                                  b => b.BonusType == BonusType.PercentPersonnelTraining)
                                                                  .Select(b => b.Amount)
                                                                  .Sum())
                                                         .Sum();
                            break;
*/
                        case ResourceType.RawMaterials:
                            modifier.Offset += buildings
                                .Where(o => o.IsActive)
                                .Select(
                                o => o.Design.Bonuses
                                         .Where(b => b.BonusType == BonusType.RawMaterials)
                                         .Select(b => b.Amount)
                                         .Sum())
                                .Sum();
                            modifier.Multiplier += 0.01f
                                                   * buildings
                                                         .Where(o => o.IsActive)
                                                         .Select(
                                                         o => o.Design.Bonuses
                                                                  .Where(
                                                                  b => b.BonusType == BonusType.PercentRawMaterials)
                                                                  .Select(b => b.Amount)
                                                                  .Sum())
                                                         .Sum();
                            break;
                    }
                    return modifier;
                };

            foreach (var resourceType in EnumHelper.GetValues<ResourceType>())
            {
                var modifier = getModifier(resourceType);
                resourceLevels[resourceType] = modifier.Apply(0);
            }

            this.ResourceLevels = resourceLevels;
        }
        #endregion
    }

    [Serializable]
    public sealed class NopAction : ProductionActionBase
    {
        public NopAction(IProductionAction baseAction) : base(baseAction) {}
    }

    [Serializable]
    [DebuggerDisplay("{GetDebugText()}")]
    public sealed class WaitForTurnsAction : ProductionActionBase
    {
        private readonly int _duration;
        private int _industrySpent;

        public override int ActionDuration
        {
            get { return _duration; }
        }

        public WaitForTurnsAction([NotNull] IProductionAction baseAction, int duration)
            : base(baseAction)
        {
            if (duration < 0)
                throw new ArgumentOutOfRangeException("duration", "value must be >= 0");

            _duration = duration;
            Run();
        }

        protected override string GetDebugText()
        {
            return string.Format(
                "{{WaitForTurnsAction Duration={0}}}",
                _duration);
        }

        protected override int GetProductionOutputOverride(ProductionCategory category)
        {
            switch (category)
            {
                case ProductionCategory.Industry:
                    return (base.GetProductionOutputOverride(category) - _industrySpent);
                default:
                    return base.GetProductionOutputOverride(category);
            }
        }

        private void Run()
        {
            var resources = base.ResourceLevels;

            for (var i = 0; i < _duration; i++)
            {
                this.CurrentPopulation = Math.Min(
                    this.Colony.Population.Maximum,
                    (int)(this.CurrentPopulation * (1.0f + this.CurrentGrowthRate)));

                var newLabor = (this.CurrentPopulation / 10) - this.LaborAllocation.TotalUnits;
                if (newLabor > 0)
                    this.LaborAllocation = this.LaborAllocation.AddUnallocatedUnits(newLabor);

                var inactiveIndustryFacilities = this.Structures.GetTotalFacilities(ProductionCategory.Industry)
                                                 - this.LaborAllocation.AllocatedUnits[ProductionCategory.Industry];
                if ((inactiveIndustryFacilities > 0) && (this.LaborAllocation.UnallocatedUnits > 0))
                {
                    var facilitiesToActivate = Math.Min(
                        inactiveIndustryFacilities,
                        this.LaborAllocation.UnallocatedUnits);
                    for (var j = 0; j < facilitiesToActivate; j++)
                    {
                        this.LaborAllocation = this.LaborAllocation.Allocate(ProductionCategory.Industry);
                    }
                }

                _industrySpent = 0;

                var industry = base.GetProductionOutput(ProductionCategory.Industry);
                if (industry <= 0)
                    break;

                foreach (var resourceType in EnumHelper.GetValues<ResourceType>())
                    resources[resourceType] += this.GetResourceOutput(resourceType);

                while (!BuildProjects.IsEmpty)
                {
                    var project = BuildProjects.PeekLeft();
                    if (project.IsComplete)
                    {
                        this.BuildProjects = BuildProjects.DequeueLeft();
                        if (project.Design is BuildingDesign)
                            this.Structures = Structures.AddBuilding((BuildingDesign)project.Design);
                    }

                    var industryInvestment = project.Design.BuildCost - project.IndustryInvested;
                    if (industryInvestment > 0)
                    {
                        industryInvestment = Math.Min(industry, industryInvestment);
                        _industrySpent += industryInvestment;
                        this.BuildProjects = this.BuildProjects.DequeueLeft();
                        project = project.InvestIndustry(industryInvestment);
                        this.BuildProjects = this.BuildProjects.EnqueueLeft(project);
                        industry -= industryInvestment;
                    }

                    foreach (var resourceType in EnumHelper.GetValues<ResourceType>())
                    {
                        var resourceInvestment = project.Design.BuildResourceCosts[resourceType] -
                                                 project.ResourcesInvested[resourceType];
                        if (resourceInvestment <= 0)
                            continue;
                        resourceInvestment = Math.Min(resources[resourceType], resourceInvestment);
                        resources[resourceType] -= resourceInvestment;
                        this.BuildProjects = this.BuildProjects.DequeueLeft();
                        project = project.InvestResource(resourceType, resourceInvestment);
                        this.BuildProjects = this.BuildProjects.EnqueueLeft(project);
                    }

                    if (project.IsComplete)
                    {
                        this.BuildProjects = BuildProjects.DequeueLeft();
                        if (project.Design is BuildingDesign)
                            this.Structures = Structures.AddBuilding((BuildingDesign)project.Design);
                    }

                    if (industry > 0)
                        continue;

                    //foreach (var resourceType in EnumHelper.GetValues<ResourceType>())
                    //{
                    //    if ((resources[resourceType] > 0)
                    //        && !this.BuildProjects.IsEmpty
                    //        && (this.BuildProjects.First().Design.BuildResourceCosts[resourceType] > 0))
                    //    {
                    //        continue;
                    //    }
                    //}

                    break;
                }
            }
        }
    }

    public interface IBuildSequenceStage
    {
        #region Properties and Indexers
        TechObjectDesign UltimateTarget { get; }
        TechObjectDesign ImmediateTarget { get; }
        IProductionAction FinalAction { get; }
        IEnumerable<IProductionAction> ActionSequence { get; }
        #endregion
    }

    [DebuggerDisplay("Action={FinalAction}\nImmediateTarget={ImmediateTarget.Key},UltimateTarget={UltimateTarget.Key}")]
    public sealed class BuildSequenceStage : IBuildSequenceStage
    {
        #region Fields
        public IProductionAction FinalAction
        {
            get { return _action; }
            set { _action = value; }
        }

        private IProductionAction _action;
        private int _immediateTargetId;
        private int _ultimateTargetId;
        #endregion

        #region Methods
        private static IEnumerable<IProductionAction> EnumerateActions([NotNull] IProductionAction action)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (action.BaseAction != null)
            {
                foreach (var baseAction in EnumerateActions(action.BaseAction))
                    yield return baseAction;
            }

            yield return action;
        }
        #endregion

        #region IBuildSequenceStage Members
        public TechObjectDesign UltimateTarget
        {
            get { return GameContext.Current.TechDatabase[_ultimateTargetId]; }
            set { _ultimateTargetId = (value != null) ? value.DesignID : TechObjectDesign.InvalidDesignID; }
        }

        public TechObjectDesign ImmediateTarget
        {
            get { return GameContext.Current.TechDatabase[_immediateTargetId]; }
            set { _immediateTargetId = (value != null) ? value.DesignID : TechObjectDesign.InvalidDesignID; }
        }

        public IEnumerable<IProductionAction> ActionSequence
        {
            get { return EnumerateActions(_action); }
        }
        #endregion

        public static ReadOnlyCollection<IProductionAction> GeneratePlan([NotNull] IBuildSequenceStage thisStage)
        {
            var path = AStar.FindPath(
                thisStage,
                IsBuildPlanFinished,
                CanBuildPlanContinue,
                GeneratePlanInternal,
                (previous, current) => current.FinalAction.TotalDuration - previous.FinalAction.TotalDuration,
                s => s.FinalAction.ActionDuration);

            if (path == null)
                return new ReadOnlyCollection<IProductionAction>(new List<IProductionAction>(0));

            return path
                .Select(o => o.FinalAction)
                .Where(s => !(s is NopAction))
                .ToList()
                .AsReadOnly();
        }

        private static bool IsBuildPlanFinished(IBuildSequenceStage stage)
        {
            var action = stage.FinalAction;
            return action.BuildProjects.IsEmpty &&
                   (action.Structures.Buildings.Any(o => o.Design == stage.UltimateTarget));
        }

        private static bool CanBuildPlanContinue(IBuildSequenceStage stage)
        {
            return (stage.FinalAction.CurrentPopulation != 0);
        }

        private static IEnumerable<IBuildSequenceStage> GeneratePlanInternal([NotNull] IBuildSequenceStage thisStage)
        {
            if (thisStage == null)
                throw new ArgumentNullException("thisStage");

            var action = thisStage.FinalAction;
            var ultimateTarget = thisStage.UltimateTarget;
            var immediateTarget = thisStage.ImmediateTarget ?? ultimateTarget;
            var buildingDesign = immediateTarget as BuildingDesign;

            if (immediateTarget == null)
                yield break;

            if (action.BuildProjects.IsEmpty)
            {
                if (action.Structures.Buildings.Any(o => o.Design == ultimateTarget))
                    yield break;

                if (action.Structures.Buildings.Any(o => o.Design == immediateTarget))
                {
                    yield return new BuildSequenceStage
                                 {
                                     FinalAction = action.Nop(),
                                     ImmediateTarget = ultimateTarget,
                                     UltimateTarget = ultimateTarget
                                 };
                    yield break;
                }
            }

            var techTree = GameContext.Current.TechTrees[action.Owner] ??
                           GameContext.Current.TechTrees.Default;

            if (action.BuildProjects.IsEmpty ||
                 (!action.BuildProjects.Any(o => o.Design == immediateTarget) &&
                  !action.Structures.Buildings.Any(o => o.Design == immediateTarget)))
            {
                var prereqs = immediateTarget.Prerequisites
                    .Where(
                    o => !o.Any(
                              p => action.Structures.Buildings
                                       .Select(b => b.Design.DesignID)
                                       .Concat(
                                       action.BuildProjects
                                           .Select(b => b.Design)
                                           .OfType<BuildingDesign>()
                                           .Select(d => d.DesignID))
                                       .Contains(p.DesignID)))
                    .SelectMany(p => p.Intersect(techTree));

                if (prereqs.Any())
                {
                    foreach (var prereq in prereqs)
                    {
                        yield return new BuildSequenceStage
                                     {
                                         FinalAction = action.Nop(),
                                         ImmediateTarget = prereq,
                                         UltimateTarget = ultimateTarget
                                     };
                    }
                    yield break;
                }
            }

            if (!action.BuildProjects.IsEmpty && !action.BuildProjects.PeekLeft().IsComplete)
            {
                var nextAction = action.WaitForTurns(action.BuildProjects.PeekLeft().EstimatedTurnsRemaining);
                yield return new BuildSequenceStage
                             {
                                 FinalAction = nextAction,
                                 ImmediateTarget = immediateTarget,
                                 UltimateTarget = ultimateTarget
                             };
                yield break;
            }

            if (buildingDesign != null)
            {
                var initialNetEnergy = action.GetFutureNetProduction(ProductionCategory.Energy) - buildingDesign.EnergyCost;
                if (buildingDesign.EnergyCost > 0)
                {
                    var netEnergy = initialNetEnergy;

                    if (netEnergy < 0)
                    {
                        var unallocatedLabor = action.LaborAllocation.UnallocatedUnits;
                        var activeEnergyFacilities = action.LaborAllocation.AllocatedUnits[ProductionCategory.Energy];
                        var totalEnergyFacilities = action.Structures.GetTotalFacilities(ProductionCategory.Energy);

                        if ((unallocatedLabor > 0) && (activeEnergyFacilities < totalEnergyFacilities))
                        {
                            var unitOutput = action.Structures.GetFacilityDesign(ProductionCategory.Energy).UnitOutput;
                            var allocationsNeeded = -netEnergy / unitOutput;

                            if ((netEnergy % unitOutput) != 0)
                                allocationsNeeded++;

                            if (allocationsNeeded > unallocatedLabor)
                                allocationsNeeded = unallocatedLabor;

                            if (allocationsNeeded > 0)
                            {
                                var nextAction = action.AllocateLabor(
                                    ProductionCategory.Energy,
                                    allocationsNeeded);

                                yield return new BuildSequenceStage
                                             {
                                                 FinalAction = nextAction,
                                                 ImmediateTarget = immediateTarget,
                                                 UltimateTarget = ultimateTarget
                                             };
                                //yield break;
                            }
                        }

                        if (netEnergy < 0)
                        {
                            var energyBuildingDesign = techTree.BuildingDesigns
                                .Where(d => d.Bonuses.Any(b => b.BonusType == BonusType.Energy || b.BonusType == BonusType.PercentEnergy))
                                .Except(action.Structures.Buildings.Select(b => b.Design).Concat(action.BuildProjects.Select(b => b.Design).OfType<BuildingDesign>()))
                                .OrderBy(d => d.BuildCost)
                                .ThenBy(d => d.EnergyCost)
                                .FirstOrDefault();

                            if (energyBuildingDesign != null)
                            {
                                yield return new BuildSequenceStage
                                             {
                                                 FinalAction = action.Nop(),
                                                 ImmediateTarget = energyBuildingDesign,
                                                 UltimateTarget = ultimateTarget
                                             };
                                //yield break;
                            }
                        }
                        yield break;
                    }
                }
            }

            if (!action.BuildProjects.Select(o => o.Design.DesignID).Contains(immediateTarget.DesignID) &&
                !action.Colony.Buildings.Select(o => o.BuildingDesign.DesignID).Contains(immediateTarget.DesignID))
            {
                yield return new BuildSequenceStage
                             {
                                 FinalAction = action.EnqueueBuildProject(immediateTarget),
                                 ImmediateTarget = immediateTarget,
                                 UltimateTarget = ultimateTarget
                             };
            }
        }
    }
}
#endif
